#include "Game.h"

void Game::setNextPiece() {
    srand(time(NULL));
    int randomNumber = rand() % 6 + 1;
    Piece new_piece;

    switch (randomNumber) {
        case PIECE_L:
            new_piece = Piece_L();
            break;
        case PIECE_S:
            new_piece = Piece_S();
            break;
        case PIECE_T:
            new_piece = Piece_T();
            break;
        case PIECE_I:
            new_piece = Piece_I();
            break;
        case PIECE_E:
            new_piece = Piece_E();
            break;
        default:
            new_piece = Piece_O();
            break;
    }

    new_piece.setToStartPosition(_x / 2, _y / 2, _z);
    _currentPiece = _nextPiece;
    _nextPiece = new_piece;
};

void Game::fillField() {
    for (int z = 0; z < _z; ++z) {
        for (int y = 0; y < _y; ++y) {
            for (int x = 0; x < _x; ++x) {
                _field[x + _x * y + _x * _y * z] = 0;
            }
        }
    }
};

void Game::initializeField() {
    _field = (int *) malloc(_x * _y * _z * sizeof (int));

    fillField();
};

void Game::emptyCommands() {
    while(!_commands.empty()) {
        _commands.pop();
    }
};

void Game::restartGame() {
    _score = 0;
    _gameOver = false;
    emptyCommands();
    fillField();
    setNextPiece();
};

void Game::initializePieces() {
    setNextPiece();
    setNextPiece();
};

void Game::initializeGame() {
    initializeField();
    initializePieces();
};

void Game::updateScore(int score) {
  _score += score;  
};

int Game::computeScorePlane(int planes) {
  return (int) pow(5, planes) - 1;  
};

bool Game::checkIfInsideBoundaries(Point3f position) {
    return
        position.x() >= 0 && position.x() < _x &&
        position.y() >= 0 && position.y() < _y && 
        position.z() >= 0;
};

bool Game::checkIfValidPosition(Point3f position) {
    return checkIfInsideBoundaries(position) && getValueAtPosition(position.x(), position.y(), position.z()) == 0;
};

bool Game::checkIfValidMove(Axis a, Orientation o) {
    Matrix4f matrix;

    switch (a) {
        case X:
            matrix = matrix.translation(1.0 * o, 0.0, 0.0);
            break;
        case Y:
            matrix = matrix.translation(0.0, 1.0 * o, 0.0);
            break;
        case Z:
            matrix = matrix.translation(0.0, 0.0, 1.0 * o);
            break;
        default:
            break;
    }

    Point3f* positions = _currentPiece.transformPositions(matrix, MOVE);
    int size = _currentPiece.getSize();
    
    for (int i = 0; i < size; ++i) {
        if (!checkIfValidPosition(positions[i])) {
            return false;
        }
    }

    return true;
};

bool Game::checkIfValidRotation(Axis a, Orientation o) {
    Matrix4f matrix;

    switch (a) {
        case X:
            matrix = matrix.rotation((M_PI / 2.0) * o, 'X');
            break;
        case Y:
            matrix = matrix.rotation((M_PI / 2.0) * o, 'Y');
            break;
        case Z:
            matrix = matrix.rotation((M_PI / 2.0) * o, 'Z');
            break;
        default:
            break;
    }
    
    Point3f* positions = _currentPiece.transformPositions(matrix, ROTATE);
    int size = _currentPiece.getSize();

    for (int i = 0; i < size; ++i) {
        if (!checkIfValidPosition(positions[i])) {
            return false;
        }
    }

    //free((void*) positions);
    return true;
};

void Game::moveAllDown() {
    while (checkIfValidMove(Z, NEGATIVE)) {
       _currentPiece.move(Z, NEGATIVE);
    }
};

bool Game::moveOneDown() {
    if (checkIfValidMove(Z, NEGATIVE)) {
        _currentPiece.move(Z, NEGATIVE);
        return true;
    }

    return false;
};

void Game::insertCurrentPieceIntoField() {
    const Point3f* positions = _currentPiece.getTransformedPositions();
    int size = _currentPiece.getSize();

    for (int i = 0; i < size; ++i) {
        setValueAtPosition(positions[i].x(), positions[i].y(), positions[i].z(), _currentPiece.getColor());
    }
};

void Game::getCollisionPosition(Piece *piece) {
    Piece copy_piece = _currentPiece;
	_currentPiece = Piece(_currentPiece.getSize(), _currentPiece.getColor(), _currentPiece.getTransformedPositions());//getCurrentPieceCopy();
    moveAllDown();
    *piece = _currentPiece;
    _currentPiece = copy_piece;
};

bool Game::checkIfGameOver() {
    int size = _currentPiece.getSize();
    const Point3f* positions = _currentPiece.getTransformedPositions();

    for (int i = 0; i < size; ++i) {
        Point3f point = positions[i];
        if (getValueAtPosition(point.x(), point.y(), point.z() - 1) != 0) {
            return true;
        }
    }

    return false;
};

void Game::removePlane(int zeta) {
    for (int z = zeta; z < _z - 1; ++z) {
        for (int y = 0; y < _y; ++y) {
            for (int x = 0; x < _x; ++x) {
                setValueAtPosition(x, y, z, getValueAtPosition(x, y, z + 1));
            }
        }
    }
};

bool Game::checkIfPlaneFull(int z) {
    for (int y = 0; y < _y; ++y) {
        for (int x = 0; x < _x; ++x) {
            if (getValueAtPosition(x, y, z) == 0) {
                return false;
            }
        }
    }

    return true;
};

void Game::removeFullPlanes() {
    int removed = 0;
    
    for (int i = 0; i < _z; ++i) {
        if (checkIfPlaneFull(i)) {
            removePlane(i);
            ++removed;
            i = -1;
        }
    }
    
    updateScore(computeScorePlane(removed));
};

void Game::rotatePiece(Axis a, Orientation o) {
    if (checkIfValidRotation(a, o)) {
        _currentPiece.rotate(a, o);
    }
};

void Game::movePiece(Axis a, Orientation o) {
    if (checkIfValidMove(a, o)) {
        _currentPiece.move(a, o);
    }
};

void Game::update(Axis a, Orientation o, Operation operation) {
    bool moved = true;
    
    switch(operation) {
        case ROTATE:
            rotatePiece(a, o);
            break;
        case MOVE:
            movePiece(a, o);
            break;
        case FALL:
            moveAllDown();
        default:
            moved = moveOneDown();
            break;
    }

    if (!moved) {
        insertCurrentPieceIntoField();
        removeFullPlanes();
        setNextPiece();
        _gameOver = checkIfGameOver();
    }
};

void Game::updateAll() {
	
	 	
    while (!_commands.empty() && !_gameOver) {
        command next = _commands.front();
        _commands.pop();
      update(next.a, next.o, next.op);  
    }
    update(X, NEGATIVE, NOP);

   
};

